﻿using System;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Messaging;
using System.Runtime.Remoting.Proxies;

namespace POCOFirst
{
	public class AOPCalculator : MarshalByRefObject
	{
		private AOPCalculator()
		{ }

		public static AOPCalculator CreateInstance()
		{
			// execute method body by reflection
			//return new Proxy(typeof(AOPCalculator)).GetTransparentProxy() as AOPCalculator;
			
			// execute method body by injected object
			//return new Proxy(new AOPCalculator()).GetTransparentProxy() as AOPCalculator;

			// add two decorators.
			//var calculator = new AOPCalculator();
			//var proxy = Activator.CreateInstance(typeof(Proxy), calculator) as RealProxy;
			//var proxyCalculator = proxy.GetTransparentProxy() as AOPCalculator;
			//var logger = Activator.CreateInstance(typeof (LoggingProxy), proxyCalculator) as RealProxy;

			//return logger.GetTransparentProxy() as AOPCalculator;

			var calculator = new AOPCalculator();
			var proxy = new MessageChainProxy(calculator);
			proxy.AppendSinkType(typeof(LoggingProxySink));

			return proxy.GetTransparentProxy() as AOPCalculator;
		}

		public int Add(int x, int y)
		{
			return checked(x + y);
		}
	}

	public abstract class MessageSinkBase : IMessageSink
	{
		private readonly IMessageSink _nextSink;

		protected MessageSinkBase(IMessageSink nextSink)
		{
			this._nextSink = nextSink;
		}

		public IMessageSink NextSink
		{
			get { return _nextSink; }
		}

		public abstract IMessage SyncProcessMessage(IMessage msg);

		public virtual IMessageCtrl AsyncProcessMessage(IMessage msg, IMessageSink replySink)
		{
			return _nextSink.AsyncProcessMessage(msg, replySink);
		}
	}

	public class MessageChainProxy : RealProxy
	{
		class TerminatorSink : IMessageSink
		{
			private readonly MarshalByRefObject _target;

			public TerminatorSink(MarshalByRefObject target)
			{
				this._target = target;
			}

			IMessageSink IMessageSink.NextSink
			{
				get { return null; }
			}

			IMessage IMessageSink.SyncProcessMessage(IMessage msg)
			{
				return RemotingServices.ExecuteMessage(_target, msg as IMethodCallMessage);
			}

			IMessageCtrl IMessageSink.AsyncProcessMessage(IMessage msg, IMessageSink replySink)
			{
				throw new NotSupportedException();
			}
		}

		private MarshalByRefObject _target;
		private IMessageSink _headSink;

		public MessageChainProxy(MarshalByRefObject target)
			: base(target.GetType())
		{
			this._target = target;
			this._headSink = new TerminatorSink(target);
		}

		public override IMessage Invoke(IMessage msg)
		{
			return _headSink.SyncProcessMessage(msg);
		}

		public void AppendSinkType(Type sinkType)
		{
			var ctorArgs = new object[] { _headSink };
			var newSink = Activator.CreateInstance(sinkType, ctorArgs) as IMessageSink;
			_headSink = newSink;
		}
	}

	public class LoggingProxySink : MessageSinkBase
	{	
		public LoggingProxySink(IMessageSink sink)
		    : base(sink)
		{}

		public override IMessage SyncProcessMessage(IMessage msg)
		{
			var call = msg as IMethodCallMessage;

			Console.WriteLine("Arguments:");
			foreach (var arg in call.Args)
			{
				Console.WriteLine("{0}|{1}", arg.GetType(), arg.ToString());
			}

			// transfer the responsibility to the next sink
			return this.NextSink.SyncProcessMessage(msg);
		}
	}

	public class Proxy : RealProxy
	{
		public Proxy(Type classBeProxied)
			: base(classBeProxied)
		{ }

		private readonly MarshalByRefObject _calculator;
		public Proxy(MarshalByRefObject calculator)
			: base(calculator.GetType())
		{
			this._calculator = calculator;
		}

		public override IMessage Invoke(IMessage msg)
		{
			var dict = msg.Properties;

			foreach (var key in dict.Keys)
			{
				Console.WriteLine("{0} | {1}", key, dict[key]);
			}
			Console.WriteLine("".PadLeft(50, '-'));

			var args = dict["__Args"] as object[];
			var signatures = dict["__MethodSignature"] as Type[];

			for (var i = 0; i < args.Length; ++i)
			{
				Console.WriteLine("{0}: {1}", signatures[i], args[i]);
			}

			var call = msg as IMethodCallMessage;
			return call == null ? null : RemotingServices.ExecuteMessage(this._calculator, call);

			//var x = (int) call.InArgs[0];
			//var y = (int) call.InArgs[1];
			//var result = x + y;
			//var returnmsg = new ReturnMessage(result, null, 0, null, call) as IMessage;
			//return returnmsg;
		}
	}


}